# ---------- 文件信息 ----------#
# Elastic.py
# 这里实现的Elastic是文章“Elastic Sketch: Adaptive and Fast Network-wide Measurements”的
# Section 6.1所描述的方案，即Elastic在P4交换机上的实现形式。如果对这里的实现方案有疑问，可以
# 参考该文章。
# 作者：赵宗义，xinshengzzy@foxmail.com, 2021.01.13
# -------------------- #
from simulators.BaseSimulator import BaseSimulator
from simulators.Buckets import HWBucket, P4Bucket
from 标准库 import 累积序列, 哈希
import mytools
import 文件工具


class P4Elastic(BaseSimulator):
    def __init__(self, 内存=1.0):
        super(P4Elastic, self).__init__()
        tmp = int(内存*1024*1024/68.0)
        self.表长1 = tmp
        self.表长2 = tmp
        self.表长3 = tmp
        self.表长4 = tmp
        self.thresh = 32
        self.table = []
        for i in range(self.表长1 + self.表长2 + self.表长3 + self.表长4):
            self.table.append(P4Bucket())  # [流标识符, 正计数值, 总计数值]
        self.导出周期 = -1

    def 计算导出周期(self, 带宽消耗):
        # 此处带宽消耗的单位是bps
        哈希桶长度 = 15.0*8  # 这里默认的流标识符是IPv4的五元组, 长度单位为"位"
        self.导出周期 = (哈希桶长度*(self.表长1+self.表长2+self.表长3+self.表长4))/带宽消耗

    def 处理数据包(self, 标识符, 时间戳):
        '''处理一个新到达的数据包'''
        # 将数据包插入哈希表中
        模值 = [self.表长1, self.表长2, self.表长3, self.表长4]
        偏移量 = [0]
        for 临变 in [self.表长1, self.表长2, self.表长3]:
            偏移量.append(偏移量[-1] + 临变)
        哈希函数 = [mytools.hash1, mytools.hash2, mytools.hash3, mytools.hash4]
        计数值 = 1
        for 次序 in range(4):
            索引 = 哈希函数[次序](标识符.encode("utf-8")) % 模值[次序] + 偏移量[次序]
            if self.table[索引].empty():
                self.table[索引].init(标识符, 计数值)
                return
            elif 标识符 == self.table[索引].flowID:
                self.table[索引].match(计数值)
                return
            else:
                self.table[索引].collide(计数值)
                if self.table[索引].totVote > self.table[索引].posVote*self.thresh:
                    临时变量 = self.table[索引].getRecord()
                    self.table[索引].replace(标识符, 计数值)
                    标识符, 计数值 = 临时变量

    def 下载数据平面(self):
        流集 = dict()
        for item in self.table:
            if not item.empty():
                标识符, 计数值 = item.getRecord()
                if 1 == item.flag:
                    索引 = mytools.hash4(标识符.encode('utf-8')) % self.表长4
                    计数值 += self.cm[索引]
                if 标识符 not in 流集:
                    流集[标识符] = 0
                流集[标识符] += 计数值
        return 流集

    def 导出数据平面(self):
        流集 = self.下载数据平面()
        self.更新流集(流集)
        for 索引 in range(self.表长1 + self.表长2 + self.表长3 + self.表长4):
            self.table[索引].reset()

    def 获取流记录集(self):
        流集 = self.合并流集(self.下载数据平面(), self.流记录集)
        return 流集

    def 备份(self, 文件名):
        备份 = dict()
        备份["流记录集"] = self.获取流记录集()
        文件工具.备份数据(备份, 文件名)


class HardwareElastic(BaseSimulator):
    def __init__(self, 内存=1.0, 导出周期=-1, 协议="ipv4"):
        super(HardwareElastic, self).__init__()
        self.协议 = 协议
        self.内存 = 内存
        self.导出周期 = 导出周期
        self.thresh = 8
        self.table = []
        self.cm = []
        self.thresh = 8
        self.计算表长()
        for i in range(self.表长1 + self.表长2 + self.表长3):
            self.table.append(HWBucket())
        for i in range(self.表长4):
            self.cm.append(0)

    def 计算表长(self):
        if self.周期模式 == self.模式 and "ipv4" == self.协议:
            tmp = int(self.内存*1024*1024/22.125)
        elif self.周期模式 == self.模式 and "ipv6" == self.协议:
            tmp = int(self.内存*1024*1024/46.125)
        elif self.超时模式 == self.模式:
            tmp = int(self.内存*1024*1024/22.125)
        else:
            raise SystemExit("计算表长失败!")
        self.表长1 = int(tmp/3)
        self.表长2 = int(tmp/3)
        self.表长3 = int(tmp/3)
        self.表长4 = tmp

    def 计算导出周期(self, 带宽消耗):
        # 此处带宽消耗的单位是bps
        if "ipv4" == self.协议:
            流记录宽度 = 17.0*8  # 这里默认的流标识符是IPv4的五元组, 长度单位为"位"
        elif "ipv6" == self.协议:
            流记录宽度 = 41.0*8
        self.导出周期 = (流记录宽度*(self.表长1+self.表长2+self.表长3))/带宽消耗

    def 周期式数据包处理(self, 标识符, 时间戳):
        # 将数据包插入哈希表中
        计数值 = 1
        模值 = [self.表长1, self.表长2, self.表长3]
        偏移量 = 累积序列([0, self.表长1, self.表长2])
        for 次序 in range(3):
            索引 = 哈希(次序, 标识符) % 模值[次序] + 偏移量[次序]
            if self.table[索引].empty():
                self.table[索引].init(标识符, 计数值)
                return
            elif self.table[索引].match(标识符):
                self.table[索引].increPos(计数值)
                return
            else:
                self.table[索引].increNeg(计数值)
                if self.table[索引].negVote > self.table[索引].posVote*self.thresh:
                    临时变量 = self.table[索引].getRecord()
                    self.table[索引].replace(标识符, 计数值)
                    标识符, 计数值 = 临时变量

        # 处理第四个表
        索引 = 哈希(3, 标识符) % self.表长4
        self.cm[索引] = self.cm[索引] + 计数值

    def 超时式数据包处理(self, 标识符, 时间戳):
        模值 = [self.表长1, self.表长2, self.表长3]
        偏移量 = 累积序列([0, self.表长1, self.表长2])
        计数值 = 1
        for 次序 in range(3):
            索引 = 哈希(次序, 标识符) % 模值[次序] + 偏移量[次序]
            if self.table[索引].empty():
                self.table[索引].init(标识符, 计数值, 时间戳)
                return
            elif 标识符 == self.table[索引].flowID:
                self.table[索引].increPos(计数值, 时间戳)
                return
            else:
                self.table[索引].increNeg(计数值)
                if self.超时时间 > 0 and self.table[索引].getTs() + self.超时时间 < 时间戳:
                    流记录 = self.table[索引].getRecord()
                    self.table[索引].init(标识符, 计数值, 时间戳)
                    self.导出流记录(流记录)
                    return
                elif self.table[索引].negVote > self.table[索引].posVote*self.thresh:
                    临时变量 = self.table[索引].getContent()
                    self.table[索引].init(标识符, 计数值, 时间戳)
                    标识符, 计数值, 时间戳 = 临时变量

        '''处理第四个表'''
        # 索引 = mytools.hash4(标识符.encode('utf-8')) % self.表长4
        # self.cm[索引] = self.cm[索引] + 计数值

    def 在线流长查询(self, 标识符):
        偏移量 = 累积序列([0, self.表长1, self.表长2])
        索引1 = 哈希(0, 标识符) % self.表长1 + 偏移量[0]
        索引2 = 哈希(1, 标识符) % self.表长2 + 偏移量[1]
        索引3 = 哈希(2, 标识符) % self.表长3 + 偏移量[2]
        索引4 = 哈希(3, 标识符) % self.表长4
        flag = 0
        流长 = 0
        for 索引 in [索引1, 索引2, 索引3]:
            if 标识符 == self.table[索引].flowID:
                流长 += self.table[索引].posVote
                if 1 == self.table[索引].flag:
                    flag = 1
        if 1 == flag:
            流长 += self.cm[索引4]
        return 流长

    def 下载数据平面(self):
        流集 = dict()
        for item in self.table:
            if not item.empty():
                标识符, 计数值 = item.getRecord()
                if 1 == item.flag:
                    索引 = 哈希(3, 标识符) % self.表长4
                    计数值 += self.cm[索引]
                if 标识符 not in 流集:
                    流集[标识符] = 0
                流集[标识符] += 计数值
        return 流集

    def 重置数据平面(self):
        for 索引 in range(self.表长1 + self.表长2 + self.表长3):
            self.table[索引].reset()
        for 索引 in range(self.表长4):
            self.cm[索引] = 0

    def 备份(self, 文件名):
        备份 = dict()
        备份["流记录集"] = self.获取流记录集()
        备份["导出次数"] = self.导出次数
        文件工具.备份数据(备份, 文件名)
